﻿#include <xy/geom/camera.h>

namespace xy
{

namespace geom
{

using namespace xy::math;

camera::camera(float distance, float pitch, float yaw, float fovy, float aspect)
    : m_center(0.0f, 0.0f, 0.0f), m_worldUp(0.0f, 0.0f, 1.0f), m_distance(distance), m_pitch(pitch), m_yaw(yaw),
      m_fovy(fovy), m_aspect(aspect)
{
    _update_camera_vectors();
}

mat4x4f camera::view() const
{
    return m_viewMat;
}

mat4x4f camera::projection() const
{
    return m_projMat;
}

float camera::yaw() const
{
    return m_yaw;
}

float camera::pitch() const
{
    return m_pitch;
}

float camera::distance() const
{
    return m_distance;
}

float camera::fovy() const
{
    return m_fovy;
}

float camera::aspect() const
{
    return m_aspect;
}

vec3f camera::front() const
{
    return m_front;
}

vec3f camera::position() const
{
    return m_center - m_front * m_distance;
}

vec3f camera::center() const
{
    return m_center;
}

void camera::set_center(const vec3f &center)
{
    m_center = center;
    _update_camera_vectors();
}

void camera::set_pitch(float pitch)
{
    // 设置角度范围
    while (pitch < 0)
        pitch += 360.0f;
    while (pitch > 360.f)
        pitch -= 360.0f;

    // 根据角度设置世界方向
    int n = static_cast<int>(pitch / 180.f);
    if (n % 2 == 0)
        m_worldUp = {0.0f, 0.0f, 1.0f};
    else
        m_worldUp = {0.0f, 0.0f, -1.0f};

    m_pitch = pitch;
    _update_camera_vectors();
}

void camera::set_yaw(float yaw)
{
    m_yaw = yaw;
    _update_camera_vectors();
}

void camera::set_distance(float distance)
{
    m_distance = distance;
    _update_camera_vectors();
}

void camera::set_fovy(float fovy)
{
    m_fovy = fovy;
    _update_camera_vectors();
}

void camera::set_aspect(float aspect)
{
    m_aspect = aspect;
    _update_camera_vectors();
}

void camera::_update_camera_vectors()
{
    float ryaw = degrees_to_radians(m_yaw);
    float rpitch = degrees_to_radians(m_pitch);

    // 先算出方向，然后规范化
    m_front = -vec3f{cos(ryaw) * sin(rpitch), sin(ryaw) * sin(rpitch), cos(rpitch)}.normalize();

    // 然后叉乘计算另外两个方向
    auto right = cross(m_front, m_worldUp);
    auto mod = right.modulus();
    if (mod > std::numeric_limits<float>::epsilon())
        m_right = std::move(right) / mod;
    m_up = cross(m_right, m_front).normalize();

    m_viewMat = look_at(position(), m_center, m_up);
    m_projMat = perspective(m_fovy, m_aspect, zNear, zFar);
}

} // namespace geom

} // namespace xy
